﻿using Cylong.MongoDB.Extension;
using MongoDB.Bson.Serialization.Conventions;
using MongoDB.Driver;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Configuration;
using System.Linq.Expressions;
using System.Threading.Tasks;

namespace Cylong.MongoDB
{
    public class MongoDbService
    {
        #region 初始化

        private readonly string _connString = string.Empty;

        private static readonly ConcurrentDictionary<string, MongoClient> DIC_CLIENT = new ConcurrentDictionary<string, MongoClient>();
        private static readonly ConcurrentDictionary<Type, MongoAttribute> DIC_ATTRIBUTE = new ConcurrentDictionary<Type, MongoAttribute>();

        private MongoClient _client = null;

        /// <summary>
        /// 客户端
        /// </summary>
        public MongoClient Client
        {
            get
            {
                if (_client == null)
                {
                    _client = DIC_CLIENT.GetOrAdd(_connString, (connString) =>
                    {
                        if (string.IsNullOrWhiteSpace(_connString))
                            throw new InvalidOperationException("MongoDB连接字符串不能为空");
                        ConventionRegistry.Register("IgnoreExtraElements", new ConventionPack { new IgnoreExtraElementsConvention(true) }, type => true);
                        return new MongoClient(_connString);
                    });
                }

                return _client;
            }
        }

        //  private static readonly Lazy<MongoClient> lazyClient =new Lazy<MongoClient>(() =>     
        //  {
        //    if (string.IsNullOrWhiteSpace(_connString))
        //        throw new InvalidOperationException("MongoDB连接字符串不能为空");
        //    ConventionRegistry.Register("IgnoreExtraElements", new ConventionPack { new IgnoreExtraElementsConvention(true) }, type => true);
        //    return new MongoClient(_connString);
        //});

        //  public static MongoClient Client
        //  {
        //      get { return lazyClient.Value; }
        //  }

        private static MongoDbService _dbService;
        public static MongoDbService Instance
        {
            get
            {
                if (_dbService == null)
                {

                    _dbService = new MongoDbService();
                }
                return _dbService;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="connString">连接字符串</param>
        public MongoDbService(string connString = "MongoDb")
        {
            if (connString.ToLower().StartsWith("mongodb://"))
            {
                _connString = connString;
            }
            else
            {
                _connString = ConfigurationManager.AppSettings[connString];
            }

            if (string.IsNullOrWhiteSpace(_connString))
            {
                throw new Exception("MongoDb 配置文件错误。在 webconfig 中 appSettings 节点，添加 <add key=\"MongoDb\" value=\"mongodb://username:pwd@127.0.0.1:27017\" />");
            }
        }

        #endregion

        public IMongoCollection<T> GetCollection<T>()
        {
            var mongoAttribute = DIC_ATTRIBUTE.GetOrAdd(typeof(T), (t) =>
            {
                var attr = typeof(T).GetMongoAttribute();
                if (attr == null)
                    throw new ArgumentException("MongoAttribute不能为空");
                return attr;
            });

            var db = Client.GetDatabase(mongoAttribute.Database);
            return db.GetCollection<T>(mongoAttribute.Collection);
        }


        /// <summary>
        /// 增（异步）
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="database">库</param>
        /// <param name="collection">集合（表）</param>
        /// <param name="entity">实体(文档)</param>
        /// <returns></returns>
        public async Task AddAsync<T>(T entity) where T : class
        {
            var coll = GetCollection<T>();
            await coll.InsertOneAsync(entity);
        }

        /// <summary>
        /// 增
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="database">库</param>
        /// <param name="collection">集合（表）</param>
        /// <param name="entity">实体(文档)</param>
        /// <returns></returns>
        public void Add<T>(T entity) where T : class
        {
            var coll = GetCollection<T>();
            coll.InsertOne(entity);
        }


        /// <summary>
        /// 批量增（异步）
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="database">库</param>
        /// <param name="collection">集合（表）</param>
        /// <param name="entity">实体(文档)</param>
        public async Task BatchAddAsync<T>(List<T> entity) where T : class
        {
            var coll = GetCollection<T>();
            await coll.InsertManyAsync(entity);
        }

        /// <summary>
        /// 批量增（异步）
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="database">库</param>
        /// <param name="collection">集合（表）</param>
        /// <param name="entity">实体(文档)</param>
        public void BatchAdd<T>(List<T> entity) where T : class
        {
            var coll = GetCollection<T>();
            coll.InsertMany(entity);
        }

        /// <summary>
        /// 删
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="database">库</param>
        /// <param name="collection">集合</param>
        /// <param name="predicate">实体</param>
        /// <returns></returns>
        public async Task<long> DeleteAsync<T>(Expression<Func<T, bool>> predicate) where T : class
        {
            var coll = GetCollection<T>();
            var result = await coll.DeleteManyAsync<T>(predicate);
            return result.DeletedCount;
        }

        /// <summary>
        /// 删
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="database">库</param>
        /// <param name="collection">集合</param>
        /// <param name="predicate">实体</param>
        /// <returns></returns>
        public long Delete<T>(Expression<Func<T, bool>> predicate) where T : class
        {
            var coll = GetCollection<T>();
            var result = coll.DeleteMany<T>(predicate);
            return result.DeletedCount;
        }

        /// <summary>
        /// 更新
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="database">库</param>
        /// <param name="collection">集合</param>
        /// <param name="predicate">条件</param>
        /// <param name="lambda">实体</param>
        /// <returns></returns>
        public async Task<long> UpdateAsync<T>(Expression<Func<T, bool>> predicate, Expression<Func<T, T>> lambda)
        {
            var coll = GetCollection<T>();

            var updateDefinitionList = MongoDbExpression<T>.GetUpdateDefinition(lambda);
            var updateDefinitionBuilder = new UpdateDefinitionBuilder<T>().Combine(updateDefinitionList);
            var result = await coll.UpdateManyAsync<T>(predicate, updateDefinitionBuilder);
            return result.ModifiedCount;
        }

        /// <summary>
        /// 更新
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="database">库</param>
        /// <param name="collection">集合</param>
        /// <param name="predicate">条件</param>
        /// <param name="lambda">实体</param>
        /// <returns></returns>
        public long Update<T>(Expression<Func<T, bool>> predicate, Expression<Func<T, T>> lambda)
        {
            var coll = GetCollection<T>();

            var updateDefinitionList = MongoDbExpression<T>.GetUpdateDefinition(lambda);
            var updateDefinitionBuilder = new UpdateDefinitionBuilder<T>().Combine(updateDefinitionList);
            var result = coll.UpdateMany<T>(predicate, updateDefinitionBuilder);
            return result.ModifiedCount;
        }

        /// <summary>
        /// 更新
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="predicate">条件</param>
        /// <param name="entity">实体（根据主键更新）</param>
        /// <returns></returns>
        public async Task<long> UpdateAsync<T>(Expression<Func<T, bool>> predicate, T entity) where T : class
        {

            var coll = GetCollection<T>();

            var updateDefinitionList = entity.GetUpdateDefinition();
            var updateDefinitionBuilder = new UpdateDefinitionBuilder<T>().Combine(updateDefinitionList);
            var result = await coll.UpdateOneAsync<T>(predicate, updateDefinitionBuilder);
            return result.ModifiedCount;
        }

        /// <summary>
        /// 更新
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="predicate">条件</param>
        /// <param name="entity">实体（根据主键更新）</param>
        /// <returns></returns>
        public long Update<T>(Expression<Func<T, bool>> predicate, T entity) where T : class
        {

            var coll = GetCollection<T>();

            var updateDefinitionList = entity.GetUpdateDefinition();
            var updateDefinitionBuilder = new UpdateDefinitionBuilder<T>().Combine(updateDefinitionList);
            var result = coll.UpdateOne<T>(predicate, updateDefinitionBuilder);
            return result.ModifiedCount;
        }

        /// <summary>
        /// 查
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="predicate">查询条件</param>
        /// <param name="projector">查询字段</param>
        /// <returns></returns>
        public async Task<T> GetProjectorAsync<T>(Expression<Func<T, bool>> predicate, Expression<Func<T, T>> projector = null) where T : class
        {
            var coll = GetCollection<T>();

            var find = coll.Find(predicate);
            if (projector != null)
                find = find.Project(projector);
            return await find.FirstOrDefaultAsync();
        }


        /// <summary>
        /// 查
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="predicate">查询条件</param>
        /// <param name="projector">查询字段</param>
        /// <returns></returns>
        public T GetProjector<T>(Expression<Func<T, bool>> predicate, Expression<Func<T, T>> projector = null) where T : class
        {
            var coll = GetCollection<T>();

            var find = coll.Find(predicate);
            if (projector != null)
                find = find.Project(projector);
            return find.FirstOrDefault();
        }

        /// <summary>
        /// 查
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="predicate">查询条件</param>
        /// <param name="projector">查询字段</param>
        /// <returns></returns>
        public async Task<T> GetAsync<T>(Expression<Func<T, bool>> predicate, Expression<Func<T, object>> orderby = null, bool desc = false) where T : class
        {
            var coll = GetCollection<T>();

            var find = coll.Find(predicate);
            if (orderby != null)
                find = desc ? find.SortByDescending(orderby) : find.SortBy(orderby);

            return await find.FirstOrDefaultAsync();
        }


        /// <summary>
        /// 查
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="predicate">查询条件</param>
        /// <param name="projector">查询字段</param>
        /// <returns></returns>
        public async Task<List<TDto>> GetDtoAsync<T, TDto>(Expression<Func<T, bool>> predicate, Expression<Func<T, TDto>> projector) where T : class where TDto : class, new()
        {
            var coll = GetCollection<T>();

            var project = Builders<T>.Projection.Expression(projector);

            return await coll.Find(predicate).Project(project).ToListAsync();
        }



        /// <summary>
        /// 查
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="predicate">查询条件</param>
        /// <param name="projector">查询字段</param>
        /// <returns></returns>
        public T Get<T>(Expression<Func<T, bool>> predicate, Expression<Func<T, object>> orderby = null, bool desc = false) where T : class
        {
            var coll = GetCollection<T>();

            var find = coll.Find(predicate);
            if (orderby != null)
                find = desc ? find.SortByDescending(orderby) : find.SortBy(orderby);

            return find.FirstOrDefault();
        }


        /// <summary>
        /// 列表
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="predicate">过滤条件</param>
        /// <param name="projector">查询字段</param>
        /// <param name="limit"></param>
        /// <returns></returns>
        public async Task<List<T>> GetListAsync<T>(Expression<Func<T, bool>> predicate, Expression<Func<T, T>> projector = null, int? limit = null) where T : class
        {
            var coll = GetCollection<T>();

            var find = coll.Find(predicate);
            if (projector != null)
                find = find.Project(projector);
            if (limit != null)
                find = find.Limit(limit);
            return await find.ToListAsync();
        }


        /// <summary>
        /// 列表
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="predicate">过滤条件</param>
        /// <param name="projector">查询字段</param>
        /// <param name="limit"></param>
        /// <returns></returns>
        public List<T> GetList<T>(Expression<Func<T, bool>> predicate, Expression<Func<T, T>> projector = null, int? limit = null) where T : class
        {
            var coll = GetCollection<T>();

            var find = coll.Find(predicate);
            if (projector != null)
                find = find.Project(projector);
            if (limit != null)
                find = find.Limit(limit);
            return find.ToList();
        }


        /// <summary>
        /// 分页
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="predicate">过滤条件</param>
        /// <param name="projector">查询字段</param>
        /// <param name="pageIndex">页码</param>
        /// <param name="pageSize">页项</param>
        /// <param name="orderby">排序字段</param>
        /// <param name="desc">顺序、倒叙</param>
        /// <returns>记录,总条数</returns>
        public async Task<Tuple<List<T>, int>> GetPageListAsync<T>(Expression<Func<T, bool>> predicate, Expression<Func<T, T>> projector, int pageIndex = 1, int pageSize = 20, Expression<Func<T, object>> orderby = null, bool desc = false) where T : class
        {

            var coll = GetCollection<T>();

            var count = (int)await coll.CountDocumentsAsync<T>(predicate);
            var find = coll.Find(predicate);
            if (projector != null)
                find = find.Project(projector);
            if (orderby != null)
            {
                find = desc ? find.SortByDescending(@orderby) : find.SortBy(@orderby);
            }
            find = find.Skip((pageIndex - 1) * pageSize).Limit(pageSize);

            var list = await find.ToListAsync();

            return Tuple.Create(list, count);
        }


        /// <summary>
        /// 分页
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="predicate">过滤条件</param>
        /// <param name="projector">查询字段</param>
        /// <param name="pageIndex">页码</param>
        /// <param name="pageSize">页项</param>
        /// <param name="orderby">排序字段</param>
        /// <param name="desc">顺序、倒叙</param>
        /// <returns>记录,总条数</returns>
        public Tuple<List<T>, int> GetPageList<T>(Expression<Func<T, bool>> predicate, Expression<Func<T, T>> projector, int pageIndex = 1, int pageSize = 20, Expression<Func<T, object>> orderby = null, bool desc = false) where T : class
        {

            var coll = GetCollection<T>();

            var count = (int)coll.CountDocuments<T>(predicate);
            var find = coll.Find(predicate);
            if (projector != null)
                find = find.Project(projector);
            if (orderby != null)
            {
                find = desc ? find.SortByDescending(@orderby) : find.SortBy(@orderby);
            }
            find = find.Skip((pageIndex - 1) * pageSize).Limit(pageSize);

            var list = find.ToList();

            return Tuple.Create(list, count);
        }

    }
}
